# This testcase is part of GDB, the GNU debugger.

# Copyright 2011-2015 Free Software Foundation, Inc.

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

if { ![support_displaced_stepping] } {
    unsupported "displaced stepping"
    return -1
}

set syscall_insn ""

# Define the syscall instruction for each target.

if { [istarget "i\[34567\]86-*-linux*"] || [istarget "x86_64-*-linux*"] } {
    set syscall_insn "\[ \t\](int|syscall|sysenter)\[ \t\]"
} elseif [is_aarch32_target] {
    set syscall_insn "\[ \t\](swi|svc)\[ \t\]"
} else {
    return -1
}

proc disp_step_cross_syscall { syscall } {
    with_test_prefix "$syscall" {
	global syscall_insn
	global gdb_prompt

	set testfile "disp-step-$syscall"

	if [prepare_for_testing ${testfile}.exp ${testfile} ${testfile}.c {debug}] {
	    untested ${testfile}.exp
	    return -1
	}

	if { ![runto main] } then {
	    fail "run to main ($syscall)"
	    return
	}

	set is_target_remote [gdb_is_target_remote]

	# Delete the breakpoint on main.
	gdb_test_no_output "delete break 1"

	gdb_test "break marker" "Breakpoint.*at.* file .*${testfile}.c, line.*"
	gdb_test_no_output "set displaced-stepping off"

	set syscall_bp 0
	gdb_test_multiple "break $syscall"  "break $syscall" {
	    -re "Breakpoint (\[0-9\]*) at .*$gdb_prompt $" {
		set syscall_bp $expect_out(1,string)
		pass "break $syscall"
	    }
	}

	gdb_test "continue" "Continuing\\..*Breakpoint \[0-9\]+, (.* in |__libc_|)$syscall \\(\\).*" \
	    "continue to $syscall (1st time)"
	# Hit the breakpoint on $syscall for the first time.  In this time, we will let PLT
	# resolution done, and the number single steps we will do later will be
	# reduced.

	gdb_test "continue" "Continuing\\..*Breakpoint \[0-9\]+, (.* in |__libc_|)$syscall \\(\\).*" \
	    "continue to $syscall (2nd time)"
	# Hit the breakpoint on $syscall for the second time.  In this time, the address
	# of syscall insn and next insn of syscall are recorded.

	gdb_test "display/i \$pc" ".*"


	# Single step until we see a syscall insn or we reach the
	# upper bound of loop iterations.
	set msg "find syscall insn in $syscall"
	set steps 0
	set max_steps 1000
	gdb_test_multiple "stepi" $msg {
	    -re ".*$syscall_insn.*$gdb_prompt $" {
		pass $msg
	    }
	    -re "x/i .*=>.*\r\n$gdb_prompt $" {
		incr steps
		if {$steps == $max_steps} {
		    fail $msg
		} else {
		    send_gdb "stepi\n"
		    exp_continue
		}
	    }
	}

	if {$steps == $max_steps} {
	    return -1
	}

	set syscall_insn_addr [get_hexadecimal_valueof "\$pc" "0"]
	if {[gdb_test "stepi" "x/i .*=>.*" "stepi $syscall insn"] != 0} {
	    return -1
	}
	set syscall_insn_next_addr [get_hexadecimal_valueof "\$pc" "0"]

	gdb_test "continue" "Continuing\\..*Breakpoint \[0-9\]+, (.* in |__libc_|)$syscall \\(\\).*" \
	    "continue to $syscall (3rd time)"

	# Hit the breakpoint on $syscall for the third time.  In this time, we'll set
	# breakpoint on the syscall insn we recorded previously, and single step over it.

	set syscall_insn_bp 0
	gdb_test_multiple "break \*$syscall_insn_addr"  "break on syscall insn" {
	    -re "Breakpoint (\[0-9\]*) at .*$gdb_prompt $" {
		set syscall_insn_bp $expect_out(1,string)
		pass "break on syscall insns"
	    }
	}
	gdb_test_no_output "delete $syscall_bp" "delete break $syscall"

	gdb_test "continue" "Continuing\\..*Breakpoint \[0-9\]+, .*" \
	    "continue to syscall insn $syscall"

	gdb_test_no_output "set displaced-stepping on"

	# Check the address of next instruction of syscall.
	if {$syscall == "vfork" && $is_target_remote} {
	    setup_kfail server/13796 "*-*-*"
	}

	if {[gdb_test "stepi" "x/i .*=>.*" "single step over $syscall"] != 0} {
	    return -1
	}

	set syscall_insn_next_addr_found [get_hexadecimal_valueof "\$pc" "0"]

	set test "single step over $syscall final pc"
	if {$syscall_insn_next_addr != 0
	    && $syscall_insn_next_addr == $syscall_insn_next_addr_found} {
	    pass $test
	} else {
	    fail $test
	}

	# Delete breakpoint syscall insns to avoid interference to other syscalls.
	gdb_test_no_output "delete $syscall_insn_bp" "delete break $syscall insn"

	gdb_test "continue" "Continuing\\..*Breakpoint \[0-9\]+, marker \\(\\) at.*" \
	    "continue to marker ($syscall)"
    }
}

disp_step_cross_syscall "fork"
disp_step_cross_syscall "vfork"
